<template>
    <div id="app">
        <h3>虚拟列表</h3>
        <div ref="listWrap" class="vitual-list-wrap">
            <div class="content" :style="contentStyle">
                <div v-for="(item, index) in viewData.list" :key="index" class="item" :style="item.style">
                    {{ item.content }}
                </div>
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import { computed, CSSProperties, onMounted, reactive, ref } from "vue";
const listWrap = ref(null);
const viewData = reactive<any>({
    list: [],
    total: 1000, // 数据总条数
    height: 600, // 可视区域的高度
    rowHeight: 60, // 每条item的高度
    startIndex: 0, // 初始位置
    endIndex: 0, // 结束位置
    timer: false,
    bufferSize: 5 // 做一个缓冲

});

const contentStyle = computed<CSSProperties>(() => {
    return {
        height: `${viewData.total * viewData.rowHeight}px`,
        position: "relative",
    }
});

onMounted(() => {
    renderData();
    handleScroll(renderData);
})

// todo 设置数据
const renderData = () => {
    viewData.list = [];
    const { rowHeight, height, startIndex, total, bufferSize } = viewData;
    // 当前可视区域的row条数
    const limit = Math.ceil(height / rowHeight);
    // 可视区域的最后一个位置
    viewData.endIndex = Math.min(startIndex + limit + bufferSize, total - 1);
    for (let i = startIndex; i < viewData.endIndex; i++) {
        // @ts-ignore
        viewData.list.push({
            content: i,
            style: {
                top: `${i * rowHeight}px`
            }
        })
    }
}
// todo 监听滚动，设置statIndex与endIndex
const handleScroll = (callback: Function) => {
    // @ts-ignore
    listWrap.value && listWrap.value.addEventListener("scroll", (e) => {
        if (viewData.timer) {
            return;
        }
        const { rowHeight, startIndex, bufferSize } = viewData;
        const { scrollTop } = e.target;
        // 计算当前滚动的位置，获取当前开始的起始位置
        const currentIndex = Math.floor(scrollTop / rowHeight);
        viewData.timer = true;
        // console.log(startIndex, currentIndex);
        // 做一个简单的节流处理
        setTimeout(() => {
            viewData.timer = false;
            // 如果滑动的位置不是当前位置
            if (currentIndex !== startIndex) {
                viewData.startIndex = Math.max(currentIndex - bufferSize, 0);
                callback();
            }
        }, 500)
    })
}
</script>

<style lang="scss" scoped>
* {
    padding: 0px;
    margin: 0px;
}

#app {
    width: 300px;
    height: 428px;
    border: 1px solid #e5e5e5;
}

/*外部容器给一个固定的可视高度，并且设置可以滚动*/
.vitual-list-wrap {
    position: relative;
    height: 400px;
    overflow-y: auto;
}

/*真实容器的区域*/
.content {
    position: relative;
}

/*固定高度的每个元素*/
.item {
    height: 60px;
    padding: 10px 5px;
    border-bottom: 1px solid #111;
    position: absolute;
    left: 0;
    right: 0;
    line-height: 60px;
}
</style>